<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>任务树形表格与甘特图表</title>
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/icon.css">
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.min.js"></script>
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>

    <script type="text/javascript" src="https://www.jeasyui.com/easyui/plugins/jquery.edatagrid.min.js"></script>


    <script src="https://cdn.jsdelivr.net/npm/luxon@1.27.0/build/global/luxon.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.0.0"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
        }

        #header {
            padding: 10px;
            background: #f3f3f3;
            border-bottom: 1px solid #ccc;
        }

        #treeTable {
            width: 100%;
            height: 100%;
        }

        #ganttChart {
            width: 100%;
            height: 100%;
            border: 1px solid red
        }

        #ganttBox {
            position: relative;
            /* padding: 5px; */
            /* 添加5px的外扩 */
            box-sizing: border-box;
            /* 确保padding不会增加元素的总宽度和高度 */
            width: 100%;
            height: 100%;
        }

        .easyui-layout {
            height: 100vh;
        }

        .easyui-layout .panel-header {
            padding: 5px;
        }

        #toolbar {
            padding: 5px;
        }

        #columnMenu {
            width: 150px;
        }

        #ganttToolbar {
            background-color: #f3f3f3;
            padding: 5px;
        }
    </style>
</head>

<body>
    <div class="easyui-layout" fit="true">
        <div id="header" data-options="region:'north',split:true,border:false" style="height:60px;">

            <h2 style="margin:0;">任务树形表格与甘特图表</h2>

        </div>
        <div data-options="region:'west',split:true,title:'任务列表',width:300">
            <table id="treeTable" class="easyui-treegrid" data-options="
                    method: 'get',
                    idField: 'id',
                    treeField: 'name',
                    fitColumns: true,
                    singleSelect: true,
                    toolbar: '#toolbar',
                    fit: true
                ">
                <thead>
                    <tr>
                        <th data-options="field:'name',width:200">任务名称</th>
                        <th data-options="field:'start',width:150,align:'center'">开始时间</th>
                        <th data-options="field:'finish',width:150,align:'center'">结束时间</th>
                        <th data-options="field:'duration',width:150,align:'center'">持续时间</th>
                    </tr>
                </thead>
            </table>
            <div id="toolbar">
                <input type="file" id="xmlFile" accept=".xml" onchange="loadXMLFile(this)" style="display:none;" />
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-add" plain="true"
                    onclick="triggerFileUpload()">上传 XML 文件</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-ok" plain="true"
                    onclick="$('#columnMenu').menu('show', {left: event.pageX, top: event.pageY});">列显示</a>
            </div>
        </div>
        <div data-options="region:'center',title:'甘特图表'" style="overflow: hidden;">
            <div id="ganttToolbar">
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-add" plain="true"
                    onclick="zoomIn()">放大</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-remove" plain="true"
                    onclick="zoomOut()">缩小</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-reload" plain="true"
                    onclick="resetGantt()">重置</a>
            </div>
            <div style="width: 100%; height: calc(100% - 40px);overflow:auto; ">
                <div id="ganttBox">
                    <canvas id="ganttChart"></canvas>
                </div>
            </div>
        </div>
    </div>

    <div id="columnMenu" class="easyui-menu" style="width:150px;">
        <div data-options="name:'name',iconCls:'icon-ok'">任务名称</div>
        <div data-options="name:'start',iconCls:'icon-ok'">开始时间</div>
        <div data-options="name:'finish',iconCls:'icon-ok'">结束时间</div>
        <div data-options="name:'duration',iconCls:'icon-ok'">持续时间</div>
    </div>

    <script>
        // 触发文件上传
        function triggerFileUpload() {
            document.getElementById('xmlFile').click();
        }

        // 读取上传的 XML 文件并加载到树形表格和甘特图表
        function loadXMLFile(input) {
            const file = input.files[0];

            if (!file) {
                alert('请选择一个 XML 文件');
                return;
            }

            const reader = new FileReader();
            reader.onload = function (event) {
                const xmlText = event.target.result;
                const treeData = parseXMLToTreeData(xmlText);
                $('#treeTable').treegrid('loadData', treeData);
                renderGanttChart(treeData);
            };

            reader.readAsText(file);
        }

        // 解析 XML 文件并转换为 EasyUI 树形表格数据
        function parseXMLToTreeData(xmlText) {
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(xmlText, 'application/xml');

            const tasks = [];
            const taskNodes = xmlDoc.getElementsByTagName('Task');

            // 安全解析函数
            const getChildText = (node, tagName) => {
                const elements = node.getElementsByTagName(tagName);
                return elements.length > 0 ? elements[0].textContent : '';
            };

            for (let i = 0; i < taskNodes.length; i++) {
                const taskNode = taskNodes[i];
                const task = {
                    id: getChildText(taskNode, 'ID'),
                    name: getChildText(taskNode, 'Name'),
                    start: getChildText(taskNode, 'Start'),
                    finish: getChildText(taskNode, 'Finish'),
                    outlineLevel: parseInt(getChildText(taskNode, 'OutlineLevel')) || 1,
                    duration: getChildText(taskNode, 'Duration'),
                    children: []
                };
                tasks.push(task);
            }

            // 转换持续时间为“天 小时”格式
            tasks.forEach(task => {
                task.duration = convertDuration(task.duration);
            });

            // 构建树形结构
            const treeData = [];
            const stack = [];

            tasks.forEach(task => {
                while (stack.length > 0 && stack[stack.length - 1].outlineLevel >= task.outlineLevel) {
                    stack.pop();
                }

                if (stack.length === 0) {
                    treeData.push(task);
                } else {
                    stack[stack.length - 1].children.push(task);
                }

                stack.push(task);
            });

            // 去掉 treeData 的第一个元素
            if (treeData.length > 0) {
                treeData.shift();
            }

            return treeData;
        }

        // 将 ISO 8601 格式的持续时间转换为“天 小时”格式
        function convertDuration(duration) {
            if (!duration) return '';

            const durationRegex = /PT(\d+)H(\d+)M(\d+)S/;
            const match = duration.match(durationRegex);

            if (!match) return '';

            const hours = parseInt(match[1], 10);
            const days = Math.floor(hours / 24);
            const remainingHours = hours % 24;

            if (days > 0) {
                return `${days}天 ${remainingHours}小时`;
            } else {
                return `${remainingHours}小时`;
            }
        }

        //递归遍历树形表格数据treeData

        function traverseTreeData(treeData) {
            const teskLabels = [];
            const teskColors = ['rgba(0, 0, 255, 0.5)',
                'rgba(255, 0, 0, 0.5)',
                'rgba(0, 255, 0, 0.5)',
                'rgba(255, 255, 0, 0.5)',
                'rgba(0, 255, 255, 0.5)',
                'rgba(255, 0, 255, 0.5)',
                'rgba(128, 0, 0, 0.5)',
                'rgba(128, 128, 0, 0.5)',
                'rgba(128, 0, 128, 0.5)',
                'rgba(0, 128, 128, 0.5)',
                'rgba(0, 128, 0, 0.5)',
                'rgba(0, 0, 128, 0.5)'];

            function getChildren(item) {
                if (item.children && Array.isArray(item.children) && item.children.length > 0) {
                    item.children.forEach(child => {
                        teskLabels.push({
                            name: child.name, start: child.start, finish: child.finish,
                            color: teskColors[child.outlineLevel]
                        });
                        if (child.children && child.children.length > 0 && child.state === 'open') {
                            getChildren(child);
                        }
                    });
                }
            }
            treeData.forEach(task => {
                teskLabels.push({
                    name: task.name, start: task.start, finish: task.finish,
                    color: teskColors[task.outlineLevel]
                });
                getChildren(task);
            });
            return teskLabels
        }

        // 渲染甘特图表
        let ganttChart = null;
        let tasks = [];
        function renderGanttChart(treeData) {
            console.log(treeData);
            const ctx = document.getElementById('ganttChart').getContext('2d');
            // teskLabels = ['任务1', '任务2', '任务3'];
            tasks = traverseTreeData(treeData);

            if (ganttChart) {
                ganttChart.destroy();
            }

            ganttChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: tasks.map(task => task.name),

                },
                options: {
                    indexAxis: 'y',
                    aspectRatio: false,    // 宽度自适应
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'day',
                                tooltipFormat: 'yyyy-MM-dd'
                            },
                            min: "2025-04-13T08:00:00",
                            max: "2025-05-02T14:00:00"
                        },
                        y: {
                            ticks: {
                                autoSkip: false
                            }
                        }
                    },
                    plugins: {
                        legend: {
                            display: false
                        },
                        title: {
                            display: false,
                            text: '横道图示例'
                        }
                    },
                    elements: {
                        bar: {
                            display: false
                        }
                    }
                },
                plugins: [{
                    id: 'customGantt',
                    afterDraw: function (chart) {

                        console.log(chart);
                        const ctx = chart.ctx;
                        const xScale = chart.scales.x;
                        const yScale = chart.scales.y;

                        tasks.forEach((task, index) => {
                            // 转换日期为Luxon对象
                            const start = luxon.DateTime.fromISO(task.start);
                            const finish = luxon.DateTime.fromISO(task.finish);
                            console.log(task.name, start, finish);

                            const x = xScale.getPixelForValue(start);
                            const y = yScale.getPixelForValue(task.name) - 12;
                            const width = xScale.getPixelForValue(finish) - x;
                            const height = 24;

                            ctx.fillStyle = task.color;
                            ctx.fillRect(x, y, width, height);

                            ctx.fillStyle = '#000';
                            ctx.font = '12px Arial';
                            ctx.textAlign = 'center';
                            ctx.fillText(task.name, x + width / 2, y + height / 2 + 4);
                        });
                    }
                }]
            });

            $('#ganttBox').css({
                width: `100%`,
                height: 30 * tasks.length
            });
        }

        //////////////////////////////
        // 甘特图toolbar功能函数定义
        //////////////////////////////


        // 放大功能
        function zoomIn() {
            console.log('放大');
            // 在这里添加放大逻辑
        }

        // 缩小功能
        function zoomOut() {
            console.log('缩小');
            // 在这里添加缩小逻辑
        }

        // 重置功能
        function resetGantt() {
            console.log('重置');
            // 在这里添加重置逻辑
        }


        $(document).ready(function () {

            /////////////////////////
            // 初始化树形表格为空
            ///////////////////////////
            $('#treeTable').treegrid('loadData', []);
            $('#treeTable').treegrid({
                // 其他配置...
                onExpand: function (row) {
                    renderGanttChart($('#treeTable').treegrid('getData'));
                },
                onCollapse: function (row) {
                    renderGanttChart($('#treeTable').treegrid('getData'));
                }
            });

            // 初始化列显示菜单
            $('#columnMenu').menu({
                onClick: function (item) {
                    const fieldName = item.name;
                    const columnOpt = $('#treeTable').treegrid('getColumnOption', fieldName);
                    const isVisible = !columnOpt.hidden;
                    // console.log($(item.target));
                    // console.log(fieldName, isVisible);

                    if (isVisible) {
                        $('#treeTable').treegrid('hideColumn', fieldName);
                        $(item.target).find('.icon-ok').hide();
                    } else {
                        $('#treeTable').treegrid('showColumn', fieldName);
                        $(item.target).find('.icon-ok').show();
                    }
                }
            });


            //////////////////
            // 初始化甘特图
            //////////////////
            // const ctx = document.getElementById('ganttChart').getContext('2d');
            // let ganttChart;

        });
    </script>
</body>

</html>